package com.controller.lib.utils

import android.text.TextUtils
import android.util.Log
import timber.log.Timber
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileFilter
import java.io.FileInputStream
import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.net.URL
import java.security.DigestInputStream
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.util.Locale
import java.util.regex.Pattern
import javax.net.ssl.HttpsURLConnection

/**
 * <pre>
 * author: Blankj
 * blog  : http://blankj.com
 * time  : 2016/05/03
 * desc  : utils about file
</pre> *
 */
class FileUtils private constructor() {
  ///////////////////////////////////////////////////////////////////////////
  // interface
  ///////////////////////////////////////////////////////////////////////////
  interface OnReplaceListener {
    fun onReplace(): Boolean
  }

  init {
    throw UnsupportedOperationException("u can't instantiate me...")
  }

  companion object {
    private val LINE_SEP: String? = System.getProperty("line.separator")

    /**
     * Return the file by path.
     *
     * @param filePath The path of file.
     * @return the file
     */
    fun getFileByPath(filePath: String?): File? {
      return if (isSpace(filePath)) null else File(filePath)
    }

    /**
     * Return whether the file exists.
     *
     * @param filePath The path of file.
     * @return `true`: yes<br></br>`false`: no
     */
    fun isFileExists(filePath: String?): Boolean {
      return isFileExists(getFileByPath(filePath))
    }

    /**
     * Return whether the file exists.
     *
     * @param file The file.
     * @return `true`: yes<br></br>`false`: no
     */
    fun isFileExists(file: File?): Boolean {
      return file != null && file.exists()
    }

    /**
     * Rename the file.
     *
     * @param filePath The path of file.
     * @param newName  The new name of file.
     * @return `true`: success<br></br>`false`: fail
     */
    fun rename(filePath: String?, newName: String): Boolean {
      return rename(getFileByPath(filePath), newName)
    }

    /**
     * Rename the file.
     *
     * @param file    The file.
     * @param newName The new name of file.
     * @return `true`: success<br></br>`false`: fail
     */
    fun rename(file: File?, newName: String): Boolean {
      // file is null then return false
      if (file == null) return false
      // file doesn't exist then return false
      if (!file.exists()) return false
      // the new name is space then return false
      if (isSpace(newName)) return false
      // the new name equals old name then return true
      if (newName == file.name) return true
      val newFile = File(file.parent + File.separator + newName)
      // the new name of file exists then return false
      return !newFile.exists()
          && file.renameTo(newFile)
    }

    /**
     * Return whether it is a directory.
     *
     * @param dirPath The path of directory.
     * @return `true`: yes<br></br>`false`: no
     */
    fun isDir(dirPath: String?): Boolean {
      return isDir(getFileByPath(dirPath))
    }

    /**
     * Return whether it is a directory.
     *
     * @param file The file.
     * @return `true`: yes<br></br>`false`: no
     */
    fun isDir(file: File?): Boolean {
      return file != null && file.exists() && file.isDirectory
    }

    /**
     * Return whether it is a file.
     *
     * @param filePath The path of file.
     * @return `true`: yes<br></br>`false`: no
     */
    fun isFile(filePath: String?): Boolean {
      return isFile(getFileByPath(filePath))
    }

    /**
     * Return whether it is a file.
     *
     * @param file The file.
     * @return `true`: yes<br></br>`false`: no
     */
    fun isFile(file: File?): Boolean {
      return file != null && file.exists() && file.isFile
    }

    /**
     * Create a directory if it doesn't exist, otherwise do nothing.
     *
     * @param dirPath The path of directory.
     * @return `true`: exists or creates successfully<br></br>`false`: otherwise
     */
    fun createOrExistsDir(dirPath: String?): Boolean {
      return createOrExistsDir(getFileByPath(dirPath))
    }

    /**
     * Create a directory if it doesn't exist, otherwise do nothing.
     *
     * @param file The file.
     * @return `true`: exists or creates successfully<br></br>`false`: otherwise
     */
    fun createOrExistsDir(file: File?): Boolean {
      return file != null && (if (file.exists()) file.isDirectory else file.mkdirs())
    }

    /**
     * Create a file if it doesn't exist, otherwise do nothing.
     *
     * @param filePath The path of file.
     * @return `true`: exists or creates successfully<br></br>`false`: otherwise
     */
    fun createOrExistsFile(filePath: String?): Boolean {
      return createOrExistsFile(getFileByPath(filePath))
    }

    /**
     * Create a file if it doesn't exist, otherwise do nothing.
     *
     * @param file The file.
     * @return `true`: exists or creates successfully<br></br>`false`: otherwise
     */
    fun createOrExistsFile(file: File?): Boolean {
      if (file == null) return false
      if (file.exists()) return file.isFile
      if (!createOrExistsDir(file.parentFile)) return false
      try {
        return file.createNewFile()
      } catch (e: IOException) {
        e.printStackTrace()
        return false
      }
    }

    /**
     * Create a file if it doesn't exist, otherwise delete old file before creating.
     *
     * @param filePath The path of file.
     * @return `true`: success<br></br>`false`: fail
     */
    fun createFileByDeleteOldFile(filePath: String?): Boolean {
      return createFileByDeleteOldFile(getFileByPath(filePath))
    }

    /**
     * Create a file if it doesn't exist, otherwise delete old file before creating.
     *
     * @param file The file.
     * @return `true`: success<br></br>`false`: fail
     */
    fun createFileByDeleteOldFile(file: File?): Boolean {
      if (file == null) return false
      // file exists and unsuccessfully delete then return false
      if (file.exists() && !file.delete()) return false
      if (!createOrExistsDir(file.parentFile)) return false
      try {
        return file.createNewFile()
      } catch (e: IOException) {
        e.printStackTrace()
        return false
      }
    }

    /**
     * Copy the directory.
     *
     * @param srcDirPath  The path of source directory.
     * @param destDirPath The path of destination directory.
     * @return `true`: success<br></br>`false`: fail
     */
    fun copyDir(
      srcDirPath: String?,
      destDirPath: String?
    ): Boolean {
      return copyDir(getFileByPath(srcDirPath), getFileByPath(destDirPath))
    }

    /**
     * Copy the directory.
     *
     * @param srcDirPath  The path of source directory.
     * @param destDirPath The path of destination directory.
     * @param listener    The replace listener.
     * @return `true`: success<br></br>`false`: fail
     */
    fun copyDir(
      srcDirPath: String?,
      destDirPath: String?,
      listener: OnReplaceListener?
    ): Boolean {
      return copyDir(getFileByPath(srcDirPath), getFileByPath(destDirPath), listener)
    }

    /**
     * Copy the directory.
     *
     * @param srcDir  The source directory.
     * @param destDir The destination directory.
     * @return `true`: success<br></br>`false`: fail
     */
    fun copyDir(
      srcDir: File?,
      destDir: File?
    ): Boolean {
      return copyOrMoveDir(srcDir, destDir, false)
    }

    /**
     * Copy the directory.
     *
     * @param srcDir   The source directory.
     * @param destDir  The destination directory.
     * @param listener The replace listener.
     * @return `true`: success<br></br>`false`: fail
     */
    fun copyDir(
      srcDir: File?,
      destDir: File?,
      listener: OnReplaceListener?
    ): Boolean {
      return copyOrMoveDir(srcDir, destDir, listener, false)
    }

    /**
     * Copy the file.
     *
     * @param srcFilePath  The path of source file.
     * @param destFilePath The path of destination file.
     * @return `true`: success<br></br>`false`: fail
     */
    fun copyFile(
      srcFilePath: String?,
      destFilePath: String?
    ): Boolean {
      return copyFile(getFileByPath(srcFilePath), getFileByPath(destFilePath))
    }

    /**
     * Copy the file.
     *
     * @param srcFilePath  The path of source file.
     * @param destFilePath The path of destination file.
     * @param listener     The replace listener.
     * @return `true`: success<br></br>`false`: fail
     */
    fun copyFile(
      srcFilePath: String?,
      destFilePath: String?,
      listener: OnReplaceListener?
    ): Boolean {
      return copyFile(getFileByPath(srcFilePath), getFileByPath(destFilePath), listener)
    }

    /**
     * Copy the file.
     *
     * @param srcFile  The source file.
     * @param destFile The destination file.
     * @return `true`: success<br></br>`false`: fail
     */
    fun copyFile(
      srcFile: File?,
      destFile: File?
    ): Boolean {
      return copyOrMoveFile(srcFile, destFile, false)
    }

    /**
     * Copy the file.
     *
     * @param srcFile  The source file.
     * @param destFile The destination file.
     * @param listener The replace listener.
     * @return `true`: success<br></br>`false`: fail
     */
    fun copyFile(
      srcFile: File?,
      destFile: File?,
      listener: OnReplaceListener?
    ): Boolean {
      return copyOrMoveFile(srcFile, destFile, listener, false)
    }

    /**
     * Move the directory.
     *
     * @param srcDirPath  The path of source directory.
     * @param destDirPath The path of destination directory.
     * @return `true`: success<br></br>`false`: fail
     */
    fun moveDir(
      srcDirPath: String?,
      destDirPath: String?
    ): Boolean {
      return moveDir(getFileByPath(srcDirPath), getFileByPath(destDirPath))
    }

    /**
     * Move the directory.
     *
     * @param srcDirPath  The path of source directory.
     * @param destDirPath The path of destination directory.
     * @param listener    The replace listener.
     * @return `true`: success<br></br>`false`: fail
     */
    fun moveDir(
      srcDirPath: String?,
      destDirPath: String?,
      listener: OnReplaceListener?
    ): Boolean {
      return moveDir(getFileByPath(srcDirPath), getFileByPath(destDirPath), listener)
    }

    /**
     * Move the directory.
     *
     * @param srcDir  The source directory.
     * @param destDir The destination directory.
     * @return `true`: success<br></br>`false`: fail
     */
    fun moveDir(
      srcDir: File?,
      destDir: File?
    ): Boolean {
      return copyOrMoveDir(srcDir, destDir, true)
    }

    /**
     * Move the directory.
     *
     * @param srcDir   The source directory.
     * @param destDir  The destination directory.
     * @param listener The replace listener.
     * @return `true`: success<br></br>`false`: fail
     */
    fun moveDir(
      srcDir: File?,
      destDir: File?,
      listener: OnReplaceListener?
    ): Boolean {
      return copyOrMoveDir(srcDir, destDir, listener, true)
    }

    /**
     * Move the file.
     *
     * @param srcFilePath  The path of source file.
     * @param destFilePath The path of destination file.
     * @return `true`: success<br></br>`false`: fail
     */
    fun moveFile(
      srcFilePath: String?,
      destFilePath: String?
    ): Boolean {
      return moveFile(getFileByPath(srcFilePath), getFileByPath(destFilePath))
    }

    /**
     * Move the file.
     *
     * @param srcFilePath  The path of source file.
     * @param destFilePath The path of destination file.
     * @param listener     The replace listener.
     * @return `true`: success<br></br>`false`: fail
     */
    fun moveFile(
      srcFilePath: String?,
      destFilePath: String?,
      listener: OnReplaceListener?
    ): Boolean {
      return moveFile(getFileByPath(srcFilePath), getFileByPath(destFilePath), listener)
    }

    /**
     * Move the file.
     *
     * @param srcFile  The source file.
     * @param destFile The destination file.
     * @return `true`: success<br></br>`false`: fail
     */
    fun moveFile(
      srcFile: File?,
      destFile: File?
    ): Boolean {
      return copyOrMoveFile(srcFile, destFile, true)
    }

    /**
     * Move the file.
     *
     * @param srcFile  The source file.
     * @param destFile The destination file.
     * @param listener The replace listener.
     * @return `true`: success<br></br>`false`: fail
     */
    fun moveFile(
      srcFile: File?,
      destFile: File?,
      listener: OnReplaceListener?
    ): Boolean {
      return copyOrMoveFile(srcFile, destFile, listener, true)
    }

    private fun copyOrMoveDir(
      srcDir: File?,
      destDir: File?,
      isMove: Boolean
    ): Boolean {
      return copyOrMoveDir(srcDir, destDir, object : OnReplaceListener {
        override fun onReplace(): Boolean {
          return true
        }
      }, isMove)
    }

    private fun copyOrMoveDir(
      srcDir: File?,
      destDir: File?,
      listener: OnReplaceListener?,
      isMove: Boolean
    ): Boolean {
      if (srcDir == null || destDir == null) return false
      // destDir's path locate in srcDir's path then return false
      val srcPath = srcDir.path + File.separator
      val destPath = destDir.path + File.separator
      if (destPath.contains(srcPath)) return false
      if (!srcDir.exists() || !srcDir.isDirectory) return false
      if (destDir.exists()) {
        if (listener == null || listener.onReplace()) { // require delete the old directory
          if (!deleteAllInDir(destDir)) { // unsuccessfully delete then return false
            return false
          }
        } else {
          return true
        }
      }
      if (!createOrExistsDir(destDir)) return false
      val files = srcDir.listFiles()
      for (file in files!!) {
        val oneDestFile = File(destPath + file.name)
        if (file.isFile) {
          if (!copyOrMoveFile(file, oneDestFile, listener, isMove)) return false
        } else if (file.isDirectory) {
          if (!copyOrMoveDir(file, oneDestFile, listener, isMove)) return false
        }
      }
      return !isMove || deleteDir(srcDir)
    }

    private fun copyOrMoveFile(
      srcFile: File?,
      destFile: File?,
      isMove: Boolean
    ): Boolean {
      return copyOrMoveFile(srcFile, destFile, object : OnReplaceListener {
        override fun onReplace(): Boolean {
          return true
        }
      }, isMove)
    }

    private fun copyOrMoveFile(
      srcFile: File?,
      destFile: File?,
      listener: OnReplaceListener?,
      isMove: Boolean
    ): Boolean {
      if (srcFile == null || destFile == null) return false
      // srcFile equals destFile then return false
      if (srcFile == destFile) return false
      // srcFile doesn't exist or isn't a file then return false
      if (!srcFile.exists() || !srcFile.isFile) return false
      if (destFile.exists()) {
        if (listener == null || listener.onReplace()) { // require delete the old file
          if (!destFile.delete()) { // unsuccessfully delete then return false
            return false
          }
        } else {
          return true
        }
      }
      if (!createOrExistsDir(destFile.parentFile)) return false
      try {
        return writeFileFromIS(destFile, FileInputStream(srcFile))
            && !(isMove && !deleteFile(srcFile))
      } catch (e: FileNotFoundException) {
        e.printStackTrace()
        return false
      }
    }

    /**
     * Delete the directory.
     *
     * @param filePath The path of file.
     * @return `true`: success<br></br>`false`: fail
     */
    fun delete(filePath: String?): Boolean {
      return delete(getFileByPath(filePath))
    }

    /**
     * Delete the directory.
     *
     * @param file The file.
     * @return `true`: success<br></br>`false`: fail
     */
    fun delete(file: File?): Boolean {
      if (file == null) return false
      if (file.isDirectory) {
        return deleteDir(file)
      }
      return deleteFile(file)
    }

    /**
     * Delete the directory.
     *
     * @param dirPath The path of directory.
     * @return `true`: success<br></br>`false`: fail
     */
    fun deleteDir(dirPath: String?): Boolean {
      return deleteDir(getFileByPath(dirPath))
    }

    /**
     * Delete the directory.
     *
     * @param dir The directory.
     * @return `true`: success<br></br>`false`: fail
     */
    fun deleteDir(dir: File?): Boolean {
      if (dir == null) return false
      // dir doesn't exist then return true
      if (!dir.exists()) return true
      // dir isn't a directory then return false
      if (!dir.isDirectory) return false
      val files = dir.listFiles()
      if (files != null && files.size != 0) {
        for (file in files) {
          if (file.isFile) {
            if (!file.delete()) return false
          } else if (file.isDirectory) {
            if (!deleteDir(file)) return false
          }
        }
      }
      return dir.delete()
    }

    /**
     * Delete the file.
     *
     * @param srcFilePath The path of source file.
     * @return `true`: success<br></br>`false`: fail
     */
    fun deleteFile(srcFilePath: String?): Boolean {
      return deleteFile(getFileByPath(srcFilePath))
    }

    /**
     * Delete the file.
     *
     * @param file The file.
     * @return `true`: success<br></br>`false`: fail
     */
    fun deleteFile(file: File?): Boolean {
      return file != null && (!file.exists() || file.isFile && file.delete())
    }

    /**
     * Delete the all in directory.
     *
     * @param dirPath The path of directory.
     * @return `true`: success<br></br>`false`: fail
     */
    fun deleteAllInDir(dirPath: String?): Boolean {
      return deleteAllInDir(getFileByPath(dirPath))
    }

    /**
     * Delete the all in directory.
     *
     * @param dir The directory.
     * @return `true`: success<br></br>`false`: fail
     */
    fun deleteAllInDir(dir: File?): Boolean {
      return deleteFilesInDirWithFilter(
        dir
      ) { true }
    }

    /**
     * Delete all files in directory.
     *
     * @param dirPath The path of directory.
     * @return `true`: success<br></br>`false`: fail
     */
    fun deleteFilesInDir(dirPath: String?): Boolean {
      return deleteFilesInDir(getFileByPath(dirPath))
    }

    /**
     * Delete all files in directory.
     *
     * @param dir The directory.
     * @return `true`: success<br></br>`false`: fail
     */
    fun deleteFilesInDir(dir: File?): Boolean {
      return deleteFilesInDirWithFilter(
        dir
      ) { pathname -> pathname.isFile }
    }

    /**
     * Delete all files that satisfy the filter in directory.
     *
     * @param dirPath The path of directory.
     * @param filter  The filter.
     * @return `true`: success<br></br>`false`: fail
     */
    fun deleteFilesInDirWithFilter(
      dirPath: String?,
      filter: FileFilter
    ): Boolean {
      return deleteFilesInDirWithFilter(getFileByPath(dirPath), filter)
    }

    /**
     * Delete all files that satisfy the filter in directory.
     *
     * @param dir    The directory.
     * @param filter The filter.
     * @return `true`: success<br></br>`false`: fail
     */
    fun deleteFilesInDirWithFilter(dir: File?, filter: FileFilter): Boolean {
      if (dir == null) return false
      // dir doesn't exist then return true
      if (!dir.exists()) return true
      // dir isn't a directory then return false
      if (!dir.isDirectory) return false
      val files = dir.listFiles()
      if (files != null && files.size != 0) {
        for (file in files) {
          if (filter.accept(file)) {
            if (file.isFile) {
              if (!file.delete()) return false
            } else if (file.isDirectory) {
              if (!deleteDir(file)) return false
            }
          }
        }
      }
      return true
    }

    /**
     * Return the files in directory.
     *
     * Doesn't traverse subdirectories
     *
     * @param dir The directory.
     * @return the files in directory
     */
    fun listFilesInDir(dir: File?): List<File>? {
      return listFilesInDir(dir, false)
    }

    /**
     * Return the files in directory.
     *
     * @param dirPath     The path of directory.
     * @param isRecursive True to traverse subdirectories, false otherwise.
     * @return the files in directory
     */
    /**
     * Return the files in directory.
     *
     * Doesn't traverse subdirectories
     *
     * @param dirPath The path of directory.
     * @return the files in directory
     */
    @JvmOverloads fun listFilesInDir(dirPath: String?, isRecursive: Boolean = false): List<File>? {
      return listFilesInDir(getFileByPath(dirPath), isRecursive)
    }

    /**
     * Return the files in directory.
     *
     * @param dir         The directory.
     * @param isRecursive True to traverse subdirectories, false otherwise.
     * @return the files in directory
     */
    fun listFilesInDir(dir: File?, isRecursive: Boolean): List<File>? {
      return listFilesInDirWithFilter(
        dir,
        { true }, isRecursive
      )
    }

    /**
     * Return the files that satisfy the filter in directory.
     *
     * Doesn't traverse subdirectories
     *
     * @param dirPath The path of directory.
     * @param filter  The filter.
     * @return the files that satisfy the filter in directory
     */
    fun listFilesInDirWithFilter(
      dirPath: String?,
      filter: FileFilter
    ): List<File>? {
      return listFilesInDirWithFilter(getFileByPath(dirPath), filter, false)
    }

    /**
     * Return the files that satisfy the filter in directory.
     *
     * @param dirPath     The path of directory.
     * @param filter      The filter.
     * @param isRecursive True to traverse subdirectories, false otherwise.
     * @return the files that satisfy the filter in directory
     */
    fun listFilesInDirWithFilter(
      dirPath: String?,
      filter: FileFilter,
      isRecursive: Boolean
    ): List<File>? {
      return listFilesInDirWithFilter(getFileByPath(dirPath), filter, isRecursive)
    }

    /**
     * Return the files that satisfy the filter in directory.
     *
     * @param dir         The directory.
     * @param filter      The filter.
     * @param isRecursive True to traverse subdirectories, false otherwise.
     * @return the files that satisfy the filter in directory
     */
    /**
     * Return the files that satisfy the filter in directory.
     *
     * Doesn't traverse subdirectories
     *
     * @param dir    The directory.
     * @param filter The filter.
     * @return the files that satisfy the filter in directory
     */
    @JvmOverloads fun listFilesInDirWithFilter(
      dir: File?,
      filter: FileFilter,
      isRecursive: Boolean = false
    ): List<File>? {
      if (!isDir(dir)) return null
      val list: MutableList<File> = ArrayList()
      val files = dir!!.listFiles()
      if (files != null && files.size != 0) {
        for (file in files) {
          if (filter.accept(file)) {
            list.add(file)
          }
          if (isRecursive && file.isDirectory) {
            list.addAll(listFilesInDirWithFilter(file, filter, true)!!)
          }
        }
      }
      return list
    }

    /**
     * Return the time that the file was last modified.
     *
     * @param filePath The path of file.
     * @return the time that the file was last modified
     */
    fun getFileLastModified(filePath: String?): Long {
      return getFileLastModified(getFileByPath(filePath))
    }

    /**
     * Return the time that the file was last modified.
     *
     * @param file The file.
     * @return the time that the file was last modified
     */
    fun getFileLastModified(file: File?): Long {
      if (file == null) return -1
      return file.lastModified()
    }

    /**
     * Return the charset of file simply.
     *
     * @param filePath The path of file.
     * @return the charset of file simply
     */
    fun getFileCharsetSimple(filePath: String?): String {
      return getFileCharsetSimple(getFileByPath(filePath))
    }

    /**
     * Return the charset of file simply.
     *
     * @param file The file.
     * @return the charset of file simply
     */
    fun getFileCharsetSimple(file: File?): String {
      var p = 0
      var `is`: InputStream? = null
      try {
        `is` = BufferedInputStream(FileInputStream(file))
        p = (`is`.read() shl 8) + `is`.read()
      } catch (e: IOException) {
        e.printStackTrace()
      } finally {
        try {
          `is`?.close()
        } catch (e: IOException) {
          e.printStackTrace()
        }
      }
      return when (p) {
        0xefbb -> "UTF-8"
        0xfffe -> "Unicode"
        0xfeff -> "UTF-16BE"
        else -> "GBK"
      }
    }

    /**
     * Return the number of lines of file.
     *
     * @param filePath The path of file.
     * @return the number of lines of file
     */
    fun getFileLines(filePath: String?): Int {
      return getFileLines(getFileByPath(filePath))
    }

    /**
     * Return the number of lines of file.
     *
     * @param file The file.
     * @return the number of lines of file
     */
    fun getFileLines(file: File?): Int {
      var count = 1
      var `is`: InputStream? = null
      try {
        `is` = BufferedInputStream(FileInputStream(file))
        val buffer = ByteArray(1024)
        var readChars: Int
        if (LINE_SEP!!.endsWith("\n")) {
          while ((`is`.read(buffer, 0, 1024).also { readChars = it }) != -1) {
            for (i in 0 until readChars) {
              if (buffer[i] == '\n'.code.toByte()) ++count
            }
          }
        } else {
          while ((`is`.read(buffer, 0, 1024).also { readChars = it }) != -1) {
            for (i in 0 until readChars) {
              if (buffer[i] == '\r'.code.toByte()) ++count
            }
          }
        }
      } catch (e: IOException) {
        e.printStackTrace()
      } finally {
        try {
          `is`?.close()
        } catch (e: IOException) {
          e.printStackTrace()
        }
      }
      return count
    }

    /**
     * Return the size of directory.
     *
     * @param dirPath The path of directory.
     * @return the size of directory
     */
    fun getDirSize(dirPath: String?): String {
      return getDirSize(getFileByPath(dirPath))
    }

    /**
     * Return the size of directory.
     *
     * @param dir The directory.
     * @return the size of directory
     */
    fun getDirSize(dir: File?): String {
      val len = getDirLength(dir)
      return if (len == -1L) "" else byte2FitMemorySize(len)
    }

    /**
     * Return the length of file.
     *
     * @param filePath The path of file.
     * @return the length of file
     */
    fun getFileSize(filePath: String): String {
      val len = getFileLength(filePath)
      return if (len == -1L) "" else byte2FitMemorySize(len)
    }

    /**
     * Return the length of file.
     *
     * @param file The file.
     * @return the length of file
     */
    fun getFileSize(file: File?): String {
      val len = getFileLength(file)
      return if (len == -1L) "" else byte2FitMemorySize(len)
    }

    /**
     * Return the length of directory.
     *
     * @param dirPath The path of directory.
     * @return the length of directory
     */
    fun getDirLength(dirPath: String?): Long {
      return getDirLength(getFileByPath(dirPath))
    }

    /**
     * Return the length of directory.
     *
     * @param dir The directory.
     * @return the length of directory
     */
    fun getDirLength(dir: File?): Long {
      if (!isDir(dir)) return -1
      var len: Long = 0
      val files = dir!!.listFiles()
      if (files != null && files.size != 0) {
        for (file in files) {
          len += if (file.isDirectory) {
            getDirLength(file)
          } else {
            file.length()
          }
        }
      }
      return len
    }

    /**
     * Return the length of file.
     *
     * @param filePath The path of file.
     * @return the length of file
     */
    fun getFileLength(filePath: String): Long {
      val isURL = filePath.matches("[a-zA-z]+://[^\\s]*".toRegex())
      if (isURL) {
        try {
          val conn = URL(filePath).openConnection() as HttpsURLConnection
          conn.setRequestProperty("Accept-Encoding", "identity")
          conn.connect()
          if (conn.responseCode == 200) {
            return conn.contentLength.toLong()
          }
          return -1
        } catch (e: IOException) {
          e.printStackTrace()
        }
      }
      return getFileLength(getFileByPath(filePath))
    }

    /**
     * Return the length of file.
     *
     * @param file The file.
     * @return the length of file
     */
    fun getFileLength(file: File?): Long {
      if (!isFile(file)) return -1
      return file!!.length()
    }


    /**
     * Return the file's path of directory.
     *
     * @param file The file.
     * @return the file's path of directory
     */
    fun getDirName(file: File?): String {
      if (file == null) return ""
      return getDirName(file.absolutePath)
    }

    /**
     * Return the file's path of directory.
     *
     * @param filePath The path of file.
     * @return the file's path of directory
     */
    fun getDirName(filePath: String): String {
      if (isSpace(filePath)) return ""
      val lastSep = filePath.lastIndexOf(File.separator)
      return if (lastSep == -1) "" else filePath.substring(0, lastSep + 1)
    }

    /**
     * Return the name of file.
     *
     * @param file The file.
     * @return the name of file
     */
    fun getFileName(file: File?): String {
      if (file == null) return ""
      return getFileName(file.absolutePath)
    }

    /**
     * Return the name of file.
     *
     * @param filePath The path of file.
     * @return the name of file
     */
    fun getFileName(filePath: String): String {
      if (isSpace(filePath)) return ""
      val lastSep = filePath.lastIndexOf(File.separator)
      return if (lastSep == -1) filePath else filePath.substring(lastSep + 1)
    }

    /**
     * Return the name of file without extension.
     *
     * @param file The file.
     * @return the name of file without extension
     */
    fun getFileNameNoExtension(file: File?): String {
      if (file == null) return ""
      return getFileNameNoExtension(file.path)
    }

    /**
     * Return the name of file without extension.
     *
     * @param filePath The path of file.
     * @return the name of file without extension
     */
    fun getFileNameNoExtension(filePath: String): String {
      if (isSpace(filePath)) return ""
      val lastPoi = filePath.lastIndexOf('.')
      val lastSep = filePath.lastIndexOf(File.separator)
      if (lastSep == -1) {
        return (if (lastPoi == -1) filePath else filePath.substring(0, lastPoi))
      }
      if (lastPoi == -1 || lastSep > lastPoi) {
        return filePath.substring(lastSep + 1)
      }
      return filePath.substring(lastSep + 1, lastPoi)
    }

    /**
     * Return the extension of file.
     *
     * @param file The file.
     * @return the extension of file
     */
    fun getFileExtension(file: File?): String {
      if (file == null) return ""
      return getFileExtension(file.path)
    }

    /**
     * 检查指定目录下是否存在包含特定名称的文件
     * @param directoryPath 目录路径
     * @param targetName 目标文件名（部分或完整）
     * @return 是否存在匹配的文件
     */
    fun checkFileExists(directoryPath: String, targetName: String): Boolean {
      val directory = File(directoryPath)

      // 检查目录是否存在且可读
      if (!directory.exists() || !directory.isDirectory || !directory.canRead()) {
        return false
      }

      // 遍历目录下的所有文件和子目录
      return directory.listFiles()?.any { file ->
        if (file.isDirectory) {
          // 递归检查子目录
          checkFileExists(file.absolutePath, targetName)
        } else {
          // 检查文件名是否包含目标名称（大小写敏感）
          val isContains = file.name.contains(targetName)
          Timber.d("checkFileExists:  ${file.name},${file.path},${targetName},是否包含目标名称：$isContains")
          if (isContains) {
            Log.d("TAG", "checkFileExists:  找到文件 ${file.absolutePath},${file.path}")
            file.delete()
          }
          isContains
        }
      } ?: false
    }

    /**
     * Return the extension of file.
     *
     * @param filePath The path of file.
     * @return the extension of file
     */
    fun getFileExtension(filePath: String): String {
      if (isSpace(filePath)) return ""
      val lastPoi = filePath.lastIndexOf('.')
      val lastSep = filePath.lastIndexOf(File.separator)
      if (lastPoi == -1 || lastSep >= lastPoi) return ""
      return filePath.substring(lastPoi + 1)
    }

    ///////////////////////////////////////////////////////////////////////////
    // other utils methods
    ///////////////////////////////////////////////////////////////////////////
    private val HEX_DIGITS =
      charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F')

    private fun bytes2HexString(bytes: ByteArray?): String {
      if (bytes == null) return ""
      val len = bytes.size
      if (len <= 0) return ""
      val ret = CharArray(len shl 1)
      var i = 0
      var j = 0
      while (i < len) {
        ret[j++] = HEX_DIGITS[bytes[i].toInt() shr 4 and 0x0f]
        ret[j++] = HEX_DIGITS[bytes[i].toInt() and 0x0f]
        i++
      }
      return String(ret)
    }

    private fun byte2FitMemorySize(byteNum: Long): String {
      return if (byteNum < 0) {
        "shouldn't be less than zero!"
      } else if (byteNum < 1024) {
        String.format(Locale.getDefault(), "%.3fB", byteNum.toDouble())
      } else if (byteNum < 1048576) {
        String.format(Locale.getDefault(), "%.3fKB", byteNum.toDouble() / 1024)
      } else if (byteNum < 1073741824) {
        String.format(
          Locale.getDefault(),
          "%.3fMB",
          byteNum.toDouble() / 1048576
        )
      } else {
        String.format(
          Locale.getDefault(),
          "%.3fGB",
          byteNum.toDouble() / 1073741824
        )
      }
    }

    private fun isSpace(s: String?): Boolean {
      if (s == null) return true
      var i = 0
      val len = s.length
      while (i < len) {
        if (!Character.isWhitespace(s[i])) {
          return false
        }
        ++i
      }
      return true
    }

    private fun writeFileFromIS(
      file: File,
      `is`: InputStream
    ): Boolean {
      var os: OutputStream? = null
      try {
        os = BufferedOutputStream(FileOutputStream(file))
        val data = ByteArray(8192)
        var len: Int
        while ((`is`.read(data, 0, 8192).also { len = it }) != -1) {
          os.write(data, 0, len)
        }
        return true
      } catch (e: IOException) {
        e.printStackTrace()
        return false
      } finally {
        try {
          `is`.close()
        } catch (e: IOException) {
          e.printStackTrace()
        }
        try {
          os?.close()
        } catch (e: IOException) {
          e.printStackTrace()
        }
      }
    }

    /**
     * 文件重命名
     */
    fun renameFile(oldPath: String?, newPath: String?): File? {
      if (TextUtils.isEmpty(oldPath)) {
        return null
      }
      if (TextUtils.isEmpty(newPath)) {
        return null
      }
      val oldFile = File(oldPath)
      val newFile = File(newPath)
      val b = oldFile.renameTo(newFile)
      return File(newPath)
    }

    /**
     * 使用正则表达式验证路径格式（简化版）
     */
     fun isValidPathFormat(path: String): Boolean {
      // 正则表达式说明：
      // ^ 开始
      // (/.+)|(/.+/) 以 / 开头的路径（Android/Linux）
      // | 或
      // ([a-zA-Z]:.+)|([a-zA-Z]:.+\\) Windows 盘符路径
      // [^\\/:*?"<>|]+ 合法文件名（排除非法字符）
      // (\\.[^\\/:*?"<>|]+)? 可选的文件扩展名
      // $ 结束

      val regex =
        "^(/.+|/.+/|([a-zA-Z]:.+|[a-zA-Z]:.+\\\\)[^\\\\/:*?\"<>|]+(\\\\.[^\\\\/:*?\"<>|]+)?)$"
      return Pattern.matches(regex, path)
    }

    /**
     * 判断字符串是否符合文件路径格式
     */
    private fun isPossibleFilePath(path: String?): Boolean {
      if (path == null || path.trim { it <= ' ' }.isEmpty()) {
        return false
      }
      // 常见路径格式特征：
      // - 包含文件扩展名（如.mp4/.jpg）
      // - 包含路径分隔符（/ 或 \）
      // - 以盘符（Windows）或 /（Linux/Android）开头

      // 示例：检查是否包含路径分隔符和扩展名
      return path.contains("/") && path.lastIndexOf(".") > path.lastIndexOf("/")
    }
    /**
     * 获取文件的md5
     */
    @JvmStatic fun getFileMD5(file: File): String {
      if (!file.isFile) {
        return ""
      }
      var digest: MessageDigest? = null
      var `in`: FileInputStream? = null
      val buffer = ByteArray(1024)
      var len: Int
      try {
        digest = MessageDigest.getInstance("MD5")
        `in` = FileInputStream(file)
        while ((`in`.read(buffer, 0, 1024).also { len = it }) != -1) {
          digest.update(buffer, 0, len)
        }
        `in`.close()
      } catch (e: Exception) {
        e.printStackTrace()
        return ""
      }
      return bytesToHexString(digest.digest())
    }

    fun bytesToHexString(src: ByteArray): String {
      val stringBuilder = StringBuilder("")
      if (src == null || src.size <= 0) {
        return ""
      }
      for (i in src.indices) {
        val v = src[i].toInt() and 0xFF
        val hv = Integer.toHexString(v)
        if (hv.length < 2) {
          stringBuilder.append(0)
        }
        stringBuilder.append(hv)
      }
      return stringBuilder.toString()
    }


  }

}
